import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { plainToClass } from 'class-transformer';
import * as _ from 'lodash';
import { IOption } from 'src/common/dto/option.dto';
import { MenuTypeEnum } from 'src/common/enums/menu.enum';
import { Not, Repository } from 'typeorm';
import { SysConstants } from '../../../common/const/constants';
import { CreateMenuDto } from './dto/create-menu.dto';
import { MenuQuery } from './dto/search-menu.dto';
import { UpdateMenuDto } from './dto/update-menu.dto';
import { Menu } from './entities/menu.entity';
import { MenuVO } from './vo/menu.vo';
import { Meta, RouteVO } from './vo/route.vo';

@Injectable()
export class MenusService {

  constructor(@InjectRepository(Menu) private menuRepository: Repository<Menu>) { }


  /**
   * 菜单-查询
   * @param query 
   */
  async listMenus({ keywords }: MenuQuery): Promise<MenuVO[]> {

    const build = this.menuRepository.createQueryBuilder('menu');

    if (keywords) {
      build.andWhere('menu.name like :keywords', { keywords: `%${keywords}%` })
    }

    // const total = await build.getCount()
    const menus: Menu[] = await build.orderBy('menu.sort', 'ASC').getMany()

    const parentIds: Set<number> = new Set(menus.map(m => m.parentId))
    const menuIds: Set<number> = new Set(menus.map(m => m.id))
    // 获取根节点ID
    const rootIds = Array.from(parentIds).filter(pid => !menuIds.has(pid))

    return rootIds.flatMap(rootId => this.buildMenuTree(rootId, menus))
  }

  private buildMenuTree(parentId: number, menuList: Menu[]): MenuVO[] {

    return menuList.filter(menu => menu.parentId == parentId)
      .map(entity => {
        const menuVO: MenuVO = plainToClass(MenuVO, entity);
        menuVO.type = entity.type
        const children: MenuVO[] = this.buildMenuTree(entity.id, menuList);
        menuVO.children = [...children];
        return menuVO;
      });
  }

  /**
   * 菜单-创建
   * @param menuForm 
   * @returns 
   */
  async create(menuForm: CreateMenuDto) {

    const path: string = menuForm.path;
    const menuType: MenuTypeEnum = menuForm.type;

    // 如果是目录
    if (menuType == MenuTypeEnum.CATALOG) {
      if (menuForm.parentId == 0 && !path.startsWith("/")) {
        menuForm.path = "/" + path; // 一级目录需以 / 开头
      }
      menuForm.component = "Layout";
    }
    // 如果是外链
    else if (menuType == MenuTypeEnum.EXTLINK) {
      menuForm.component = null;
    }

    const entity: Menu = plainToClass(Menu, menuForm);
    const treePath: string = await this.generateMenuTreePath(menuForm.parentId);
    entity.treePath = treePath;



    return this.saveOrUpdate(entity);
  }

  /**
   * 生成父节点ID
   * @param parentId  父ID
   * @returns  父节点路径以英文逗号(, )分割，eg: 1,2,3
   */
  private async generateMenuTreePath(parentId: number): Promise<string> {
    if (SysConstants.ROOT_NODE_ID === parentId) {
      return String(parentId);
    } else {
      const parent: Menu = await this.findOne(parentId);
      return parent != null ? parent.treePath + "," + parent.id : null;
    }
  }


  /**
   * 菜单下拉数据
   */
  async listMenuOptions(): Promise<IOption<string, number>[]> {
    const menuList: Menu[] = await this.menuRepository.find({ order: { sort: 'ASC' } })

    return this.buildMenuOptions(SysConstants.ROOT_NODE_ID, menuList)
  }
  /**
* 递归生成菜单下拉层级列表
*
* @param parentId 父级ID
* @param menuList 菜单列表
* @return 菜单下拉列表
*/
  private buildMenuOptions(parentId: number, menuList: Menu[]): IOption<string, number>[] {


    return menuList.map((menu, index, arr) => {
      if (menu.parentId == parentId) {
        const option: IOption<string, number> = new IOption()
        option.label = menu.name
        option.value = menu.id;

        const children = this.buildMenuOptions(+menu.id, arr)
        if (Array.isArray(children) && children.length !== 0) {
          option.children = [...children.filter(m => m)]
        }

        return option;
      }
    }).filter(m => m)
  }


  /**
   * 路由列表
   * @returns 
   */
  async listRoutes(): Promise<RouteVO[]> {
    const menuList: Menu[] = await this.menuRepository.find({ where: { type: Not(MenuTypeEnum.BUTTON) }, relations: { role: true }, order: { sort: 'ASC' } })
    return this.buildRoutes(SysConstants.ROOT_NODE_ID, menuList)
  }

  /**
 * 递归生成路由列表
 * @param parentId 
 * @param menuList 
 * @returns 
 */
  private buildRoutes(parentId: number, menuList: Menu[]): RouteVO[] {


    return menuList.map((menu, index, arr) => {
      if (+menu.parentId === parentId) {
        const routeVo: RouteVO = this.toRouteVo(menu)

        const children = this.buildRoutes(+menu.id, arr)
        if (Array.isArray(children) && children.length !== 0) {
          routeVo.children = [...children.filter(m => m)]
        }

        return routeVo;
      }
    }).filter(m => m)
  }






  toRouteVo(menu: Menu): RouteVO {

    let routeVo = new RouteVO()
    routeVo.name = _.camelCase(menu.path)
    routeVo.path = menu.path
    routeVo.redirect = menu.redirect
    routeVo.component = menu.component
    const meta = new Meta()
    meta.title = menu.name
    meta.icon = menu.icon
    meta.roles = menu.role.map(r => r.code)
    //是否隐藏
    meta.hidden = menu.visible === 0
    //【菜单】是否开启页面缓存
    meta.keepAlive = MenuTypeEnum.MENU === menu.type && menu.keepAlive === 1
    // 【目录】只有一个子路由是否始终显示
    meta.alwaysShow = MenuTypeEnum.CATALOG === menu.type && menu.alwaysShow === 1
    routeVo.meta = meta
    return routeVo
  }

  findAll() {
    return this.menuRepository.find();
  }

  findOne(id: number) {
    return this.menuRepository.findOne({
      where: {
        id
      }
    });
  }

  /**
   * 菜单-修改
   * @param updateMenuDto 
   * @returns 
   */
  async update(updateMenuDto: UpdateMenuDto) {

    return this.saveOrUpdate(plainToClass(Menu, updateMenuDto));
  }


  private async saveOrUpdate(menu: Menu) {
    //存在ID就修改
    if (+menu.id) {
      const qmenu = await this.findOne(+menu.id);
      const newMenu = await this.menuRepository.merge(qmenu, menu);
      return this.menuRepository.save(newMenu);
    } else {
      return this.menuRepository.save(menu)
    }
  }

  remove(id: number) {
    return this.menuRepository.delete(id);
  }
}


